<?php

/**
 * Beaver_Model_Mapper_Db
 *
 * @category   Beaver
 * @package    Beaver
 * @subpackage Beaver_Model_Mapper
 * @uses       Beaver_Model_Mapper_Interface
 * @author     Mihály Kuprivecz
 */
class Beaver_Model_Mapper_Db implements Beaver_Model_Mapper_Interface {
	/**
	 * @var Zend_Db_Table_Abstract
	 */
	protected $_dbTable;
	
	/**
	 * Reference to the model
	 * @var Beaver_Model_Abstract
	 */
	protected $_model;
	
	protected $_validationDefaults = array(
		"tinyint" => array(
			array(
				"type" => "int",
				"message" => "Model_Mapper_Db_validation_int"
			),
			array(
				"type" => "between",
				"attributes" => array(
					"min" => 0,
					"max" => 255
				),
				"message" => "Model_Mapper_Db_validation_int_beetwen"
			)
		),
		"smallint" => array(
			array(
				"type" => "int",
				"message" => "Model_Mapper_Db_validation_int"
			),
			array(
				"type" => "between",
				"attributes" => array(
					"min" => 0,
					"max" => 65535
				),
				"message" => "Model_Mapper_Db_validation_int_beetwen"
			)
		),
		"mediumint" => array(
			array(
				"type" => "int",
				"message" => "Model_Mapper_Db_validation_int"
			),
			array(
				"type" => "between",
				"attributes" => array(
					"min" => 0,
					"max" => 16777215
				),
				"message" => "Model_Mapper_Db_validation_int_beetwen"
			)
		),
		"int" => array(
			array(
				"type" => "int"
			),
			array(
				"type" => "between",
				"attributes" => array(
					"min" => 0,
					"max" => PHP_INT_MAX
				),
				"message" => "Model_Mapper_Db_validation_int_beetwen"
			)
		),
		"bigint" => array(
			array(
				"type" => "int",
				"message" => "Model_Mapper_Db_validation_int"
			),
			array(
				"type" => "between",
				"attributes" => array(
					"min" => 0,
					"max" => PHP_INT_MAX
				),
				"message" => "Model_Mapper_Db_validation_int_beetwen"
			)
		),
		"datetime" => array(
			array(
				"type" => "regex",
				"attributes" => array(
					"pattern" => "/^([0-9]{2,4})-([0-1][0-9])-([0-3][0-9]) (?:([0-2][0-9]):([0-5][0-9]):([0-5][0-9]))?$/"
				),
				"message" => "Model_Mapper_Db_validation_datetime"
			)
		),
		"date" => array(
			array(
				"type" => "regex",
				"attributes" => array(
					"pattern" => "^([1-3][0-9]{3,3})-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2][1-9]|3[0-1])$"
				),
				"message" => "Model_Mapper_Db_validation_date"
			)
		),
		"time" => array(
			array(
				"type" => "regex",
				"attributes" => array(
					"pattern" => "^([0-1][0-9]|2[0-4]):([0-5][0-9]):([0-5][0-9])$"
				),
				"message" => "Model_Mapper_Db_validation_time"
			)
		),
	);
	
	/**
	 * Set the reference to the model
	 * @param Beaver_Model_Abstract $model
	 * @return Beaver_Model_Mapper_Db
	 */
	public function setModel(Beaver_Model_Abstract $model) {
		$this->_model = $model;
		return $this;
	}
	
	/**
	 * Get the reference to the model
	 * @return  Beaver_Model_Abstract
	 */
	public function getModel() {
		return $this->_model;
	}
	
	/**
	 * Specify Zend_Db_Table instance to use for data operations
	 *
	 * @param  Zend_Db_Table_Abstract $dbTable
	 * @return Beaver_Model_DbMapper
	 */
	public function setDbTable() {
		$this->_dbTable = new Zend_Db_Table ( strtolower ( $this->getModel ()->getModelName () ) );
		return $this;
	}
	
	/**
	 * Get registered Zend_Db_Table instance
	 * @return Zend_Db_Table_Abstract
	 */
	public function getDbTable() {
		if (null === $this->_dbTable) {
			$this->setDbTable ();
		}
		return $this->_dbTable;
	}
	
	/**
	 * Set the model attributes from the tale info
	 * @return void
	 */
	public function addModelAttributesFromSource() {
		$tableInfo = $this->getDbTable()->info ();
		
		foreach ( $tableInfo ["cols"] as $name ) {
			$this->getModel ()->addAttribute ( $name );

			if(
				!$tableInfo ["metadata"][$name]["NULLABLE"] &&
				null === $tableInfo ["metadata"][$name]["DEFAULT"] &&
				$name != "id" &&
				$name != "created" &&
				$name != "modified"
			)
			{
				$this->getModel()->setAttributeToRequired($name);
			}
			if( $this->getModel()->getValidatorsFromMapper())
			{
				$this->addValidateFromColumn($name);
			}
		}
	}
	
	/**
	 * Set the model validators from the tale info
	 * @return void
	 */
	public function addValidateFromColumn($name)
	{
		$tableInfo = $this->getDbTable()->info ();
		
		$type = $tableInfo ["metadata"][$name]["DATA_TYPE"];

		if (isset($this->_validationDefaults[$type]))
		{
			foreach ($this->_validationDefaults[$type] as $validate)
			{
				if (!isset($validate["attributes"])) $validate["attributes"] = array();
				if (!isset($validate["message"])) $validate["message"] = null;
				$this->getModel()->addValidation($name, $validate["type"], $validate["message"], $validate["attributes"]);
			}
		}
		if(
			"varchar" == $tableInfo ["metadata"][$name]["DATA_TYPE"] ||
			"char" == $tableInfo ["metadata"][$name]["DATA_TYPE"]
		)
		{
			$maxChar = intval($tableInfo ["metadata"][$name]["LENGTH"]);
			$this->getModel()->addValidation($name, "stringLength", "Model_Mapper_Db_validation_stringLength", array("min" => 0, "max" => $maxChar));
		}
	}
	
	/**
	 * Save an entry
	 *
	 * @return integer the inserted primary key or the updated row numbers
	 */
	public function save() {
		$model = $this->getModel ();
		$data  = $model->getAttributes();
		foreach ($data as $name => $value) {
			if (null === $value) unset($data[$name]); 
		};
		$tableInfo = $this->getDbTable()->info ();
		$tableMeta = $tableInfo ["metadata"];
		
		// if field is empty and the column in table is nullable, write a NULL value
		foreach ($data as $attribute => $value ) {
			if( empty($value) && $tableMeta[$attribute]["NULLABLE"] )
			{
				$data[$attribute] = new Zend_Db_Expr("NULL");
			}
		}
		
		$create = true;
		if ( isset($data["id"]) && null !== $data["id"] && $this->getDbTable ()->find ( $data["id"])->count() > 0)
		{
			$create = false;
		}
		if ( empty($data) )
		{
			unset( $data ['id'] );
		}
		if (
			isset($tableMeta['modified']) &&
			(
				!isset($data['modified']) || 
				empty($data['modified']) ||
				is_null($data['modified'])
			)
		)
		{
			$data['modified'] = date("Y-m-d H:i:s");
			$attributes = $model->getAttributes();
			if (isset($attributes['modified']))
			{
				$model->modified = $data['modified'];
			}
		}
		if ( $create ) {
			if (
				isset($tableMeta['created']) &&
				(
					!isset($data['created']) || 
					empty($data['created']) ||
					is_null($data['created'])
				) 
			)
			{
				$data['created'] = date("Y-m-d H:i:s");
				$attributes = $model->getAttributes();
				if (isset($attributes['created']))
				{
					$model->created = $data['created'];
				}
			}
			$result = $this->getDbTable ()->insert ( $data );
		} else {
			$result = $this->getDbTable ()->update ( $data, array ('id = ?' => $data['id'] ) );
			if ( $result == 0 )
			{
				$result = false;
			}
			else
			{
				$result = $data["id"];
			}
		}
		if( is_numeric($result) ) $result = (int) $result;
		return $result;
	}
	
	
	/**
	 * Delete an entry
	 *
	 * @param  integer
	 * @return integer deleted row numbers
	 */
	public function delete($id = null) {
		if (null === $id)
		{
			$model = $this->getModel();
			$id    = $model->id;
		}
		$where  = $this->getDbTable()->getAdapter()->quoteInto('id = ?', $id);
		$result = $this->getDbTable()->delete($where);
		if ( $result > 0 && isset($model) )
		{
			$model->clearAttributes();
		}
		return $result;
	}
	
	
	/**
	 * Find an entry by id
	 *
	 * @param  int $id
	 * @return 
	 */
	public function find($id = null) {
		if (null === $id)
		{
			$model      = $this->getModel ();
			$attributes = $model->getAttributes();
			$result     = $this->getDbTable ()->find ( $model->id );
			$result     = $result->current()->toArray();
			foreach ($attributes as $name => $value) {
				if ( isset($result[$name]) ) $model->$name = $result[$name];
			}
		}
		if ( is_array($id) )
		{
			$result = $this->getDbTable ()->find ( $id );
			$entries = array();
			foreach ($result as $row)
			{
				$model = clone $this->getModel ();
				$attributes = $model->getAttributes();
				foreach ($attributes as $name => $value) {
					if ( isset($row->$name) ) $model->$name = $row->$name;
				}
				$entries [] = $model;
			}
			return $entries;
		}
		else
		{
			$result = $this->getDbTable ()->find ( $id );
			$model = $this->getModel();
			if (0 == count($result)) {
				return;
			}
			$row = $result->current();
			
			$attributes = $model->getAttributes();
			
			foreach ($attributes as $name => $value) {
				if ( isset($row->$name) ) $model->$name = $row->$name;
			}	
		}
	}
	
	/**
	 * Fetch all entries ( as model ) to array 
	 *
	 * @return array
	 */
	public function fetchAll( $filter = null, $order = null, $count = null, $offset = null ) {
		$where = $this->getWhereStement($filter);
		$resultSet = $this->getDbTable ()->fetchAll ($where, $order, $count, $offset);
		$entries = array ();
		foreach ( $resultSet as $row ) {
			$model = clone $this->getModel ();
			$attributes = $model->getAttributes();
			foreach ($attributes as $name => $value) {
				if ( isset($row->$name) ) $model->$name = $row->$name;
			}
			$entries [] = $model;
		}
		return $entries;
	}
	
	/**
	 * Fetch all entries to array 
	 *
	 * @return array
	 */
	public function findAll( $filter = null, $order = null, $count = null, $offset = null ) {
		$where = $this->getWhereStement($filter);
		$resultSet = $this->getDbTable ()->fetchAll ($where, $order, $count, $offset);
		$entries = array ();
		foreach ( $resultSet as $row ) {
			$entries [] = $row->toArray();
		}
		return $entries;
	}
	
	public function findCount( $filter = null ) {
		$where = $this->getWhereStement($filter);
		$sql = $this->getDbTable ()->select()->from($this->getDbTable (), 'COUNT(*)');
		if ( null != $where )
		{
			$sql->where( $where );
		}
		$result = $this->getDbTable ()->fetchRow($sql)->toArray();
		return $result["COUNT(*)"];
	}
	
	protected function getWhereStement($filter)
	{
		$statement = null;
		if ( is_array($filter) && count($filter) > 0 )
		{
			$statement = '';

			for ($i = 0; $i < count($filter); $i++)
			{
				$filterset = $filter[$i];
				switch( $filterset["type"] )
				{
					case 'string':
						switch( $filterset["comparsion"] )
						{
							case 'eq':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' = ?', $filterset["value"]);
								break;
							case 'left':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' LIKE ?', '%' . $filterset["value"]);
								break;
							case 'right':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' LIKE ?', $filterset["value"] . '%');
								break;
							case 'all':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' LIKE ?', '%' . $filterset["value"] . '%');
								break;
						}							
						break;
					case 'numeric':
						switch( $filterset["comparsion"] )
						{
							case 'ne':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' != ?', $filterset["value"]);
								break;
							case 'eq':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' = ?', $filterset["value"]);
								break;
							case 'lt':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' < ?', $filterset["value"]);
								break;
							case 'gt':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' > ?', $filterset["value"]);
								break;
						}
						break;
					case 'date':
						switch( $filterset["comparsion"] )
						{
							case 'ne':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' != ?', date('Y-m-d',strtotime($filterset["value"])));
								break;
							case 'eq':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' = ?', date('Y-m-d',strtotime($filterset["value"])));
								break;
							case 'lt':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' < ?', date('Y-m-d',strtotime($filterset["value"])));
								break;
							case 'gt':
								$statement.= $this->getDbTable ()->getAdapter()->quoteInto( $filterset["attribute"] . ' > ?', date('Y-m-d',strtotime($filterset["value"])));
								break;
						}
						break;
				}
				if ( ( count($filter) - 1 ) == $i && count($filter) > 1 )
				{
					$statement.= "AND ";
				}
			}//var_dump($statement);
			//exit;
		}
		return $statement;
	}
}
